home *** CD-ROM | disk | FTP | other *** search
- /*
- * linux/drivers/block/falhd.c
- *
- * Copyright (C) 1991, 1992 Linus Torvalds
- *
- */
-
- /*
- * This is the low-level hd interrupt support. It traverses the
- * request-list, using interrupts to jump between functions. As
- * all the functions are called within interrupts, we may not
- * sleep. Special care is recommended.
- *
- * modified by Drew Eckhardt to check nr of hd's from the CMOS.
- *
- * Thanks to Branko Lankester, lankeste@fwi.uva.nl, who found a bug
- * in the early extended-partition checks and added DM partitions
- *
- *
- * Modified 1994 for ATARI IDE controller support by Bjoern Brauel
- */
-
- #include <linux/errno.h>
- #include <linux/signal.h>
- #include <linux/sched.h>
- #include <linux/timer.h>
- #include <linux/fs.h>
- #include <linux/kernel.h>
- #include <linux/genhd.h>
-
- #include <linux/bootinfo.h>
- #include <linux/interrupt.h>
- #include <linux/atarihw.h>
- #include <linux/atarihdreg.h>
- #include <linux/atariints.h>
-
- #include <asm/system.h>
- #include <asm/io.h>
- #include <asm/segment.h>
-
- #define MAJOR_NR HD_MAJOR
- #include "blk.h"
-
- static int revalidate_hddisk(int, int);
-
- #define MAX_ERRORS 16 /* Max read/write errors/sector */
- #define RESET_FREQ 8 /* Reset controller every 8th retry */
- #define RECAL_FREQ 4 /* Recalibrate every 4th retry */
- #define MAX_HD 2
-
- static void recal_intr(void);
- static void bad_rw_intr(void);
-
- static char recalibrate[ MAX_HD ] = { 0, };
- static int access_count[MAX_HD] = {0, };
- static char busy[MAX_HD] = {0, };
- static struct wait_queue * busy_wait = NULL;
-
- static int reset = 0;
- static int hd_error = 0;
-
- /*
- * This struct defines the HD's and their types.
- */
- struct hd_i_struct {
- unsigned int head,sect,cyl,wpcom,lzone,ctl;
- };
- struct hd_i_struct hd_info[] = { {0,0,0,0,0,0},{0,0,0,0,0,0} };
- static int NR_HD = 0;
-
- static struct hd_struct hd[MAX_HD<<6]={{0,0},};
- static int hd_sizes[MAX_HD<<6] = {0, };
- static int hd_blocksizes[MAX_HD<<6] = {0, };
-
- #define HD_ID_RETRIES 500000
-
- #define port_read(port, buf, nr) \
- __asm__ __volatile__ \
- ("movel %0,a0; \
- movel %1,a1; \
- movel %2,d6; \
- 1:movew a0@,a1@+; \
- movew a0@,a1@+; \
- movew a0@,a1@+; \
- movew a0@,a1@+; \
- movew a0@,a1@+; \
- movew a0@,a1@+; \
- movew a0@,a1@+; \
- movew a0@,a1@+; \
- movew a0@,a1@+; \
- movew a0@,a1@+; \
- movew a0@,a1@+; \
- movew a0@,a1@+; \
- movew a0@,a1@+; \
- movew a0@,a1@+; \
- movew a0@,a1@+; \
- movew a0@,a1@+; \
- dbra d6,1b" : : "i" (port), "g" (buf), "g" (nr) : "a0", "a1", "d6");
-
- #define port_write(port, buf, nr) \
- __asm__ __volatile__ \
- ("movel %0,a0; \
- movel %1,a1; \
- movel %2,d6; \
- 1:movew a1@+,a0@; \
- movew a1@+,a0@; \
- movew a1@+,a0@; \
- movew a1@+,a0@; \
- movew a1@+,a0@; \
- movew a1@+,a0@; \
- movew a1@+,a0@; \
- movew a1@+,a0@; \
- movew a1@+,a0@; \
- movew a1@+,a0@; \
- movew a1@+,a0@; \
- movew a1@+,a0@; \
- movew a1@+,a0@; \
- movew a1@+,a0@; \
- movew a1@+,a0@; \
- movew a1@+,a0@; \
- dbra d6,1b" : : "i" (port), "g" (buf), "g" (nr) : "a0", "a1", "d6");
-
- void hd_setup(char *str, int *ints)
- {
- int hdind = 0;
-
- if (ints[0] != 3)
- return;
- if (hd_info[0].head != 0)
- hdind=1;
- hd_info[hdind].head = ints[2];
- hd_info[hdind].sect = ints[3];
- hd_info[hdind].cyl = ints[1];
- hd_info[hdind].wpcom = 0;
- hd_info[hdind].lzone = ints[1];
- hd_info[hdind].ctl = 0;
- }
-
- static int win_result(void)
- {
- int i=inb_p(HD_STATUS);
-
- if ((i & (BUSY_STAT | READY_STAT | WRERR_STAT | SEEK_STAT | ERR_STAT))
- == (READY_STAT | SEEK_STAT)) {
- hd_error = 0;
- return 0; /* ok */
- }
- printk("HD: win_result: status = 0x%02x\n",i);
- if (i&1) {
- hd_error = inb(HD_ERROR);
- printk("HD: win_result: error = 0x%02x\n",hd_error);
- }
- return 1;
- }
-
- static int controller_busy(void);
- static int status_ok(void);
-
- static int controller_ready(unsigned int drive, unsigned int head)
- {
- int retry = 100;
-
- do {
- if (controller_busy() & BUSY_STAT)
- return 0;
- outb_p(0xA0 | (drive<<4) | head, HD_CURRENT);
- if (status_ok())
- return 1;
- } while (--retry);
- return 0;
- }
-
- static int status_ok(void)
- {
- unsigned char status = inb_p(HD_STATUS);
-
- if (status & BUSY_STAT)
- return 1;
- if (status & WRERR_STAT)
- return 0;
- if (!(status & READY_STAT))
- return 0;
- if (!(status & SEEK_STAT))
- return 0;
- return 1;
- }
-
- static int controller_busy(void)
- {
- int retries = 100000;
- unsigned char status;
-
- do {
- status = inb_p(HD_STATUS);
- } while ((status & BUSY_STAT) && --retries);
- return status;
- }
-
- static void hd_out(unsigned int drive,unsigned int nsect,unsigned int sect,
- unsigned int head,unsigned int cyl,unsigned int cmd,
- void (*intr_addr)(void))
- {
- if (drive>1 || head>15)
- panic("Trying to write bad sector");
- if (reset)
- return;
- if (!controller_ready(drive, head)) {
- reset = 1;
- return;
- }
- SET_INTR(intr_addr);
- outb(hd_info[drive].ctl,HD_CMD);
- outb_p(hd_info[drive].wpcom>>2,HD_PRECOMP);
- outb_p(nsect,HD_NSECTOR);
- outb_p(sect,HD_SECTOR);
- outb_p(cyl,HD_LCYL);
- outb_p(cyl>>8,HD_HCYL);
- outb_p(0xA0|(drive<<4)|head,HD_CURRENT);
- outb_p(cmd,HD_COMMAND);
- }
-
- static int drive_busy(void)
- {
- unsigned int i;
- unsigned char c;
-
- for (i = 0; i < 500000 ; i++) {
- c = inb_p(HD_STATUS);
- c &= (BUSY_STAT | READY_STAT | SEEK_STAT);
- if (c == (READY_STAT | SEEK_STAT))
- return 0;
- }
- printk("HD controller times out, status = 0x%02x\n",c);
- return 1;
- }
-
- static void reset_controller(void)
- {
- int i;
-
- printk("HD-controller reset\n");
- outb(4,HD_CMD);
- for(i = 0; i < 1000; i++) nop();
- outb(hd_info[0].ctl & 0x0f ,HD_CMD);
- if (drive_busy())
- printk("HD-controller still busy\n");
- if ((hd_error = inb(HD_ERROR)) != 1)
- printk("HD-controller reset failed: %02x\n",hd_error);
- }
-
- static void reset_hd(void)
- {
- static int i;
-
- repeat:
- if (reset) {
- reset = 0;
- i = -1;
- reset_controller();
- } else if (win_result()) {
- bad_rw_intr();
- if (reset)
- goto repeat;
- }
- i++;
- if (i < NR_HD) {
- hd_out(i,hd_info[i].sect,hd_info[i].sect,hd_info[i].head-1,
- hd_info[i].cyl,WIN_SPECIFY,&reset_hd);
- if (reset)
- goto repeat;
- } else
- do_hd_request();
- }
-
- /*
- * Ok, don't know what to do with the unexpected interrupts: on some machines
- * doing a reset and a retry seems to result in an eternal loop. Right now I
- * ignore it, and just set the timeout.
- */
- void unexpected_hd_interrupt(void)
- {
- printk("Unexpected HD interrupt\n");
- SET_TIMER;
- }
-
- /*
- * bad_rw_intr() now tries to be a bit smarter and does things
- * according to the error returned by the controller.
- * -Mika Liljeberg (liljeber@cs.Helsinki.FI)
- */
- static void bad_rw_intr(void)
- {
- int dev;
-
- if (!CURRENT)
- return;
- dev = MINOR(CURRENT->dev) >> 6;
- if (++CURRENT->errors >= MAX_ERRORS || (hd_error & BBD_ERR)) {
- end_request(0);
- recalibrate[dev] = 1;
- } else if (CURRENT->errors % RESET_FREQ == 0)
- reset = 1;
- else if ((hd_error & TRK0_ERR) || CURRENT->errors % RECAL_FREQ == 0)
- recalibrate[dev] = 1;
- /* Otherwise just retry */
- }
-
- static inline int wait_DRQ(void)
- {
- int retries = 100000;
-
- while (--retries > 0)
- if (inb_p(HD_STATUS) & DRQ_STAT)
- return 0;
- return -1;
- }
-
- #define STAT_MASK (BUSY_STAT | READY_STAT | WRERR_STAT | SEEK_STAT | ERR_STAT)
- #define STAT_OK (READY_STAT | SEEK_STAT)
-
- static void read_intr(void)
- {
- int i;
- int retries = 100000;
-
- do {
- i = (unsigned) inb_p(HD_STATUS);
- if (i & BUSY_STAT)
- continue;
- if ((i & STAT_MASK) != STAT_OK)
- break;
- if (i & DRQ_STAT)
- goto ok_to_read;
- } while (--retries > 0);
- printk("HD: read_intr: status = 0x%02x\n",i);
- if (i & ERR_STAT) {
- hd_error = (unsigned) inb(HD_ERROR);
- printk("HD: read_intr: error = 0x%02x\n",hd_error);
- }
- bad_rw_intr();
- do_hd_request();
- return;
- ok_to_read:
- port_read(HD_DATA,CURRENT->buffer,15);
- CURRENT->errors = 0;
- CURRENT->buffer += 512;
- CURRENT->sector++;
- i = --CURRENT->nr_sectors;
- --CURRENT->current_nr_sectors;
- #ifdef DEBUG
- printk("hd%d : sector = %d, %d remaining to buffer = %08x\n",
- MINOR(CURRENT->dev), CURRENT->sector, i, CURRENT->
- buffer);
- #endif
- if (!i || (CURRENT->bh && !SUBSECTOR(i)))
- end_request(1);
- if (i > 0) {
- SET_INTR(&read_intr);
- return;
- }
- (void) inb_p(HD_STATUS);
- #if (HD_DELAY > 0)
- last_req = read_timer();
- #endif
- do_hd_request();
- return;
- }
-
- static void write_intr(void)
- {
- int i;
- int retries = 100000;
-
- do {
- i = (unsigned) inb_p(HD_STATUS);
- if (i & BUSY_STAT)
- continue;
- if ((i & STAT_MASK) != STAT_OK)
- break;
- if ((CURRENT->nr_sectors <= 1) || (i & DRQ_STAT))
- goto ok_to_write;
- } while (--retries > 0);
- printk("HD: write_intr: status = 0x%02x\n",i);
- if (i & ERR_STAT) {
- hd_error = (unsigned) inb(HD_ERROR);
- printk("HD: write_intr: error = 0x%02x\n",hd_error);
- }
- bad_rw_intr();
- do_hd_request();
- return;
- ok_to_write:
- CURRENT->sector++;
- i = --CURRENT->nr_sectors;
- --CURRENT->current_nr_sectors;
- CURRENT->buffer += 512;
- if (!i || (CURRENT->bh && !SUBSECTOR(i)))
- end_request(1);
- if (i > 0) {
- SET_INTR(&write_intr);
- port_write(HD_DATA,CURRENT->buffer,15);
- } else {
- do_hd_request();
- }
- return;
- }
-
- static void recal_intr(void)
- {
- if (win_result())
- bad_rw_intr();
- do_hd_request();
- }
-
- /*
- * This is another of the error-routines I don't know what to do with. The
- * best idea seems to just set reset, and start all over again.
- */
- static void hd_times_out(void)
- {
- DEVICE_INTR = NULL;
- reset = 1;
- if (!CURRENT)
- return;
- printk("HD timeout\n");
- if (++CURRENT->errors >= MAX_ERRORS) {
- #ifdef DEBUG
- printk("hd : too many errors.\n");
- #endif
- end_request(0);
- }
-
- do_hd_request();
- }
-
- /*
- * The driver has been modified to enable interrupts a bit more: in order to
- * do this we first (a) disable the timeout-interrupt and (b) clear the
- * device-interrupt. This way the interrupts won't mess with out code (the
- * worst that can happen is that an unexpected HD-interrupt comes in and
- * sets the "reset" variable and starts the timer)
- */
- static void do_hd_request(void)
- {
- unsigned int block,dev;
- unsigned int sec,head,cyl,track;
- unsigned int nsect;
-
- if (CURRENT && CURRENT->dev < 0) return;
-
- if (DEVICE_INTR)
- return;
- repeat:
- timer_active &= ~(1<<HD_TIMER);
- INIT_REQUEST;
- dev = MINOR(CURRENT->dev);
- block = CURRENT->sector;
- nsect = CURRENT->nr_sectors;
- if (dev >= (NR_HD<<6) || block >= hd[dev].nr_sects) {
- #ifdef DEBUG
- printk("hd%d : attempted read for sector %d past end of device at sector %d.\n",
- block, hd[dev].nr_sects);
- #endif
- end_request(0);
- goto repeat;
- }
- block += hd[dev].start_sect;
- dev >>= 6;
- sec = block % hd_info[dev].sect + 1;
- track = block / hd_info[dev].sect;
- head = track % hd_info[dev].head;
- cyl = track / hd_info[dev].head;
- #ifdef DEBUG
- printk("hd%d : cyl = %d, head = %d, sector = %d, buffer = %08x\n",
- dev, cyl, head, sec, CURRENT->buffer);
- #endif
- if (reset) {
- int i;
-
- for (i=0; i < NR_HD; i++)
- recalibrate[i] = 1;
- reset_hd();
- return;
- }
- if (recalibrate[dev]) {
- recalibrate[dev] = 0;
- hd_out(dev,hd_info[dev].sect,0,0,0,WIN_RESTORE,&recal_intr);
- if (reset)
- goto repeat;
- return;
- }
- if (CURRENT->cmd == WRITE) {
- hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,&write_intr);
- if (reset)
- goto repeat;
- if (wait_DRQ()) {
- printk("HD: do_hd_request: no DRQ\n");
- bad_rw_intr();
- goto repeat;
- }
- port_write(HD_DATA,CURRENT->buffer,15);
- return;
- }
- if (CURRENT->cmd == READ) {
- hd_out(dev,nsect,sec,head,cyl,WIN_READ,&read_intr);
- if (reset)
- goto repeat;
- return;
- }
- panic("unknown hd-command");
- }
-
- static int hd_ioctl(struct inode * inode, struct file * file,
- unsigned int cmd, unsigned long arg)
- {
- struct hd_geometry *loc = (struct hd_geometry *) arg;
- int dev, err;
-
- if (!inode)
- return -EINVAL;
- dev = MINOR(inode->i_rdev) >> 6;
- if (dev >= NR_HD)
- return -EINVAL;
- switch (cmd) {
- case HDIO_GETGEO:
- if (!loc) return -EINVAL;
- err = verify_area(VERIFY_WRITE, loc, sizeof(*loc));
- if (err)
- return err;
- put_fs_byte(hd_info[dev].head,
- (char *) &loc->heads);
- put_fs_byte(hd_info[dev].sect,
- (char *) &loc->sectors);
- put_fs_word(hd_info[dev].cyl,
- (short *) &loc->cylinders);
- put_fs_long(hd[MINOR(inode->i_rdev)].start_sect,
- (long *) &loc->start);
- return 0;
- case BLKGETSIZE: /* Return device size */
- if (!arg) return -EINVAL;
- err = verify_area(VERIFY_WRITE, (long *) arg, sizeof(long));
- if (err)
- return err;
- put_fs_long(hd[MINOR(inode->i_rdev)].nr_sects,
- (long *) arg);
- return 0;
- case BLKFLSBUF:
- if(!suser()) return -EACCES;
- if(!inode->i_rdev) return -EINVAL;
- fsync_dev(inode->i_rdev);
- invalidate_buffers(inode->i_rdev);
- return 0;
-
- case BLKRRPART: /* Re-read partition tables */
- return revalidate_hddisk(inode->i_rdev, 1);
- RO_IOCTLS(inode->i_rdev,arg);
- default:
- return -EINVAL;
- }
- }
-
- static int hd_open(struct inode * inode, struct file * filp)
- {
- int target;
- target = DEVICE_NR(MINOR(inode->i_rdev));
-
- while (busy[target])
- sleep_on(&busy_wait);
- access_count[target]++;
- return 0;
- }
-
- /*
- * Releasing a block device means we sync() it, so that it can safely
- * be forgotten about...
- */
- static void hd_release(struct inode * inode, struct file * file)
- {
- int target;
- sync_dev(inode->i_rdev);
-
- target = DEVICE_NR(MINOR(inode->i_rdev));
- access_count[target]--;
-
- }
-
- static void hd_geninit(void);
-
- static struct gendisk hd_gendisk = {
- MAJOR_NR, /* Major number */
- "hd", /* Major name */
- 6, /* Bits to shift to get real from partition */
- 1 << 6, /* Number of partitions per real */
- MAX_HD, /* maximum number of real */
- hd_geninit, /* init function */
- hd, /* hd struct */
- hd_sizes, /* block sizes */
- 0, /* number */
- (void *) hd_info, /* internal */
- NULL /* next */
- };
-
- /*te******************************************************************
- *
- * hd_interrupt routine
- * Test, if this is a harddisk interrupt and call
- * the irq handler
- * Otherwise ignore this interrupt.
- *
- ********************************************************/
- static void hd_interrupt (struct intframe *fp, void *data)
- {
- void (*hd_irq_handler)(void) = DEVICE_INTR;
-
- /* Test, if this is a harddisk interrupt. MSB(0xdd2030) = 1 */
- if (inb (HD_IRQ_TEST) & 0x7f)
- {
- DEVICE_INTR = NULL;
- timer_active &= ~(1 << HD_TIMER);
-
- if (!hd_irq_handler)
- hd_irq_handler = unexpected_hd_interrupt;
- hd_irq_handler ();
-
- return;
- }
- }
-
- /*te******************************************************************
- *
- * hd_geninit
- * Scan and initialize all connected harddisks.
- *
- ***************************/
- static void hd_geninit (void)
- {
- int i;
- int drive;
- unsigned short Identify [256];
- unsigned char drive_found;
-
- printk ("Probing harddisk(s):\n");
- NR_HD = 0;
-
- outb (IDE_DISABLE_IRQ, HD_CMD); /* Disable HD-interrupt */
- cli (); /* Disable interrupts */
-
- for (drive=0; drive<2; drive++)
- {
- if (drive_busy ())
- {
- printk ("hd: Can't access harddisk controller.\n");
- NR_HD = 0;
- break;
- }
-
- /* select drive */
- outb_p ((unsigned char) (0xA0 | (drive<<4)), HD_CURRENT);
-
- /* Set identify command */
- outb_p (WIN_IDENTIFY, HD_COMMAND);
-
- drive_found = 0;
- /* Test, if ready to read */
- for (i=0; i<HD_ID_RETRIES; i++)
- {
- if (inb_p (HD_STATUS) & DRQ_STAT)
- {
- drive_found = 1;
- break;
- }
- }
-
- if (drive_found)
- {
- port_read (HD_DATA, Identify, 15);
-
- hd_info[drive].cyl = Identify [1];
- hd_info[drive].head = Identify [3];
- hd_info[drive].wpcom = 0;
- hd_info[drive].ctl = 0;
- hd_info[drive].lzone = Identify [1];
- hd_info[drive].sect = Identify [6];
- NR_HD++;
- Identify [47] = 0x0000;
- printk ("hd%d: %s\n", drive, (unsigned char *)&Identify [27]);
- printk (" cyl %d, sector %d, head %d.\n",
- hd_info[drive].cyl,
- hd_info[drive].sect,
- hd_info[drive].head);
- }
- else
- printk ("hd%d not found.\n", drive);
- }
- sti (); /* Enable interrupts */
-
- /* Add the interrupt handler */
- if (NR_HD)
- if (!add_isr (IRQ_MFP_FDC, hd_interrupt, 0, NULL))
- {
- printk ("HD: Can't add interrupt handler for harddisk device.\n");
- NR_HD = 0;
- }
- mfp.int_en_b |= 0x80; /* enable int */
- mfp.int_mk_b |= 0x80; /* not masked */
-
- i = NR_HD;
- while (i-- > 0)
- hd[i<<6].nr_sects = hd_info[i].head * hd_info[i].sect
- * hd_info[i].cyl;
-
- hd_gendisk.nr_real = NR_HD;
-
- for (i=0; i<(MAX_HD << 6); i++)
- hd_blocksizes[i] = 1024;
-
- blksize_size [MAJOR_NR] = hd_blocksizes;
- }
-
- static struct file_operations hd_fops = {
- NULL, /* lseek - default */
- block_read, /* read - general block-dev read */
- block_write, /* write - general block-dev write */
- NULL, /* readdir - bad */
- NULL, /* select */
- hd_ioctl, /* ioctl */
- NULL, /* mmap */
- hd_open, /* open */
- hd_release, /* release */
- block_fsync /* fsync */
- };
-
- unsigned long hd_init(unsigned long mem_start, unsigned long mem_end)
- {
- if (boot_info.machtype!=MACH_ATARI || boot_info.bi_atari.model!=ATARI_FALCON)
- return mem_start;
- if (register_blkdev(MAJOR_NR,"hd",&hd_fops)) {
- printk("Unable to get major %d for harddisk\n",MAJOR_NR);
- return mem_start;
- }
- blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
- read_ahead[MAJOR_NR] = 8; /* 8 sector (4kB) read-ahead */
- hd_gendisk.next = gendisk_head;
- gendisk_head = &hd_gendisk;
- timer_table[HD_TIMER].fn = hd_times_out;
- return mem_start;
- }
-
- #define DEVICE_BUSY busy[target]
- #define USAGE access_count[target]
- #define CAPACITY (hd_info[target].head*hd_info[target].sect*hd_info[target].cyl)
- /* We assume that the the bios parameters do not change, so the disk capacity
- will not change */
- #undef MAYBE_REINIT
- #define GENDISK_STRUCT hd_gendisk
-
- /*
- * This routine is called to flush all partitions and partition tables
- * for a changed scsi disk, and then re-read the new partition table.
- * If we are revalidating a disk because of a media change, then we
- * enter with usage == 0. If we are using an ioctl, we automatically have
- * usage == 1 (we need an open channel to use an ioctl :-), so this
- * is our limit.
- */
- static int revalidate_hddisk(int dev, int maxusage)
- {
- int target, major;
- struct gendisk * gdev;
- int max_p;
- int start;
- int i;
-
- target = DEVICE_NR(MINOR(dev));
- gdev = &GENDISK_STRUCT;
-
- cli();
- if (DEVICE_BUSY || USAGE > maxusage) {
- sti();
- return -EBUSY;
- };
- DEVICE_BUSY = 1;
- sti();
-
- max_p = gdev->max_p;
- start = target << gdev->minor_shift;
- major = MAJOR_NR << 8;
-
- for (i=max_p - 1; i >=0 ; i--) {
- sync_dev(major | start | i);
- invalidate_inodes(major | start | i);
- invalidate_buffers(major | start | i);
- gdev->part[start+i].start_sect = 0;
- gdev->part[start+i].nr_sects = 0;
- };
-
- #ifdef MAYBE_REINIT
- MAYBE_REINIT;
- #endif
-
- gdev->part[start].nr_sects = CAPACITY;
- resetup_one_dev(gdev, target);
-
- DEVICE_BUSY = 0;
- wake_up(&busy_wait);
- return 0;
- }
-